<?php

declare(strict_types=1);

namespace app\model\esxsys;

use app\eslib\CommonUtils;
use app\eslib\HttpUtils;
use think\facade\Cache;
use think\facade\Db;
use think\Model;


/**
 * 定时任务表 数据模型类
 * 需要安装依赖：
 */
class CrontabsModel extends Model
{
    // 设置当前模型对应的完整数据表名称
    protected $table = 'esxsys_crontabs';

    // 自动时间戳
    protected $autoWriteTimestamp = 'int';

    // 定义时间戳字段名
    protected $createTime = 'created_at';
    protected $updateTime = 'updated_at';

    /**
     * 任务类型 字段内容
     */
    protected $crontab_type_data = ['url' => '请求URL', 'sql' => '执行SQL', 'shell' => '执行Shell'];

    /**
     * 返回 任务类型 字段内容
     */
    public function getCrontabTypeData()
    {
        return $this->crontab_type_data;
    }

    /**
     * 返回 任务类型 字段获取器的值
     */
    public function getCrontabTypeTextAttr($value)
    {
        $value = $this->getData('crontab_type');
        return isset($this->crontab_type_data[$value]) ? $this->crontab_type_data[$value] : $value;
    }

    /**
     * 状态 字段内容
     */
    protected $status_data = [0 => '禁用', 1 => '启用', 2 => '过期', 3 => '完成'];

    /**
     * 返回 状态 字段内容
     */
    public function getStatusData($useForForm = false)
    {
        return $useForForm ? [0 => '禁用', 1 => '启用'] : $this->status_data;
    }

    /**
     * 返回 状态 字段获取器的值
     */
    public function getStatusTextAttr($value)
    {
        $value = intval($this->getData('status'));
        return isset($this->status_data[$value]) ? $this->status_data[$value] : $value;
    }

    /**
     * 获取 表达式 规则 指定次数的执行时间
     */
    public static function getNextExecTime(string $express, int $count = 1, $begin_time = 'now')
    {
        $times = [];
        $nextTime = new \DateTime($begin_time);
        $cron = new \Cron\CronExpression($express);
        while ($count > 0) {
            $nextTime = $cron->getNextRunDate($nextTime);
            $times[] = $nextTime->format('Y-m-d H:i:s');
            $count--;
        }
        return $times;
    }

    /**
     * 执行命令
     */
    public static function execCommand($type, $crontab_content)
    {
        // 任务类型(url=请求URL,sql=执行SQL,shell=执行Shell)
        switch ($type) {
            case 'url':
                return HttpUtils::get($crontab_content);
            case 'sql':
                $sqls = array_filter(preg_split("/;[ \t]{0,}\n/i", $crontab_content));
                if (!$sqls) {
                    return CommonUtils::stdMessage(false, '没有SQL命令');
                }
                // 开启事务（使用事务处理的话，需要数据库引擎支持事务处理。比如 MySQL 的 MyISAM 不支持事务处理，需要使用 InnoDB 引擎）
                Db::startTrans();
                try {
                    foreach ($sqls as $sql) {
                        $sql = trim($sql);
                        if ($sql == '') {
                            continue;
                        }
                        $prefix = substr($sql, 0, 2);
                        if ($prefix == '--' || $prefix == '/*' || $prefix == '* ' || $prefix == '*/') {
                            continue;
                        }
                        Db::execute($sql);
                    }
                    // 提交事务
                    Db::commit();
                } catch (\PDOException $ex) {
                    // 回滚事务
                    Db::rollback();
                    return CommonUtils::stdMessage(false, $ex->getMessage());
                }
                return CommonUtils::stdMessage(true, 'SQL命令执行完成');
            case 'shell':
                // $message = shell_exec($crontab_content);
                // $result = $message ? true : false;
                $return_val = 1; // 0 表示成功，其他都表示失败。
                $output = [];
                exec($crontab_content, $output, $return_val);
                if ($return_val == 0) {
                    // 成功
                    return CommonUtils::stdMessage(true, 'Shell命令执行完成');
                }
                // 失败
                return CommonUtils::stdMessage(false, 'Shell命令执行失败');
            default:
                return CommonUtils::stdMessage(false, '任务类型错误');
                break;
        }
        return CommonUtils::stdMessage(true, '执行完成');
    }
    /**
     * 执行一条任务
     */
    public static function execTask($id, $crontab = null)
    {
        if (is_null($crontab)) {
            $model = self::where('id', $id)->find();
            if (!$model) {
                return;
            }
            $crontab = $model->toArray();
        }
        $time = time();
        // 状态：0=禁用,1=启用,2=过期,3=完成 
        $beginTime = $crontab['begin_time'] ? strtotime($crontab['begin_time']) : 0;
        if ($beginTime > 0 && $time < $beginTime) {
            return CommonUtils::stdMessage(false, '任务未开始');
        }
        if ($crontab['max_count'] > 0 && $crontab['exec_count'] > $crontab['max_count']) {
            self::where('id', $id)->update(['status' => 3]);
            return CommonUtils::stdMessage(false, '任务已超过最大执行次数');
        }
        $endTime = $crontab['end_time'] ? strtotime($crontab['end_time']) : 0;
        if ($endTime > 0 && $time > $endTime) {
            self::where('id', $id)->update(['status' => 2]);
            return CommonUtils::stdMessage(false, '任务已过期');
        }
        //如果未到执行时间则继续循环
        $cron = CronExpression::factory($crontab['crontab_schedule']);
        $dueTime = date("YmdHi", $time);
        // 任务不在执行时间或者任务已经执行过了
        if (!$cron->isDue($dueTime) || $dueTime === date("YmdHi", $crontab['exec_time'])) {
            return CommonUtils::stdMessage(false, '任务不在执行时间');
        }
        // 如果允许执行
        $updateData = [
            'exec_time' => $time,
            'exec_count' => $crontab['exec_count'] + 1,
        ];
        if ($crontab['max_count'] > 0 && ($updateData['exec_count'] >= $crontab['max_count'])) {
            $updateData['status'] = 3;
        }
        // 更新任务信息
        self::where('id', $id)->update($updateData);
        $msgInfo = self::execCommand($crontab['crontab_type'], $crontab['crontab_content']);
        // 写入日志
        EsxdmSysCrontabLogModel::createRecord($id, $crontab['crontab_title'], $time, $msgInfo['msg'], $msgInfo['success']);
        return $msgInfo;
    }
    /**
     * 自动运行所有任务
     */
    public static function autorun()
    {
        $cacheName = 'crontab';
        $list = Cache::get($cacheName);
        if (!$list) {
            $list = self::where('status', 1)->select()->toArray();
            Cache::set($cacheName, $list, 3600 * 24);
        }
        if (!$list) {
            return;
        }
        foreach ($list as $row) {
            $msgInfo = self::execTask(0, $row);
            var_export($msgInfo);
        }
    }
}
